/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package br.ufpr.inf.escel.testing.tools;

import br.escel.jabuti.cmdtool.CreateProject;
import br.escel.jabuti.cmdtool.ImportTestCase;
import br.escel.jabuti.cmdtool.JabutiReport;
import br.escel.jabuti.cmdtool.ProberInstrum;
import br.jabuti.junitexec.JUnitJabutiCore;
import br.ufpr.inf.escel.brain.model.Individual;
import br.ufpr.inf.escel.brain.model.po.DadoTestePO;
import br.ufpr.inf.escel.consts.Constantes;
import br.ufpr.inf.escel.manager.util.Arquivo;
import br.ufpr.inf.escel.testing.model.Cut;
import br.ufpr.inf.escel.testing.model.CutPO;
import br.ufpr.inf.escel.testing.tools.impl.Ferramenta;
import java.io.File;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * Passos da Jabuti
 * 1 - Criar projeto
 * 2 - instrumentar classe
 * 3 - importar dado de teste
 * 4 - executar dado de teste
 * 5 - gerar relatório
 * 6 - extrair resultados
 * @author gustavo
 */
public class Jabuti extends Ferramenta {

    private CutPO cut;

    @Override
    public void setCut(Cut cut) {
        this.cut = (CutPO) cut;
    }

    @Override
    public double run(Individual ind) {
        boolean isProjetoGerado = false;
        Long temposDeExecucao = 0L;
        for (int numDado = 0; numDado < ind.getCapacidade(); numDado++) {
            DadoTestePO dadoTeste = (DadoTestePO) ind.getDadoTeste(numDado);

            dadoTeste.setId(numDado);
            dadoTeste.setIndividuoId(ind.getId());

            String pastaIndividuo = cut.getNovoPath() + "ind" + ind.getId() + File.separator;

            if (!isProjetoGerado(pastaIndividuo) && !isProjetoGerado) {
                System.out.println("Criar projeto ind" + ind.getId());
                criarProjeto();
                instrumentarCut();
                isProjetoGerado = true;
            }

            File arquivoJUnit = dadoTeste.gerarArquivoJUnit(dadoTeste);
            dadoTeste.compilarArquivoJUnit(arquivoJUnit);

            extrairInformacoes(dadoTeste);
            temposDeExecucao += executarDadoTeste(dadoTeste);
            atualizarProjeto(ind);

            gerarRelatorio(ind, "tc");
            extrairResultados(ind);
            System.out.println("Resultado dt: " + extrairResultados(ind));

            dadoTeste.setFitness(extrairResultados(ind));
        }

        gerarRelatorio(ind, "pr");
        System.out.println("Resultado ind: " + extrairResultados(ind));

        Double resultado = extrairResultados(ind);
        resultado = (resultado == 0) ? ind.getObjectiveSpace()[0] : resultado;
        temposDeExecucao = (temposDeExecucao == 0) ? 10L : temposDeExecucao;

        double[] objetivos = {resultado, temposDeExecucao};

        ind.setObjectiveSpace(objetivos);
        ind.moverArquivos();
        return resultado;
    }

    /**
     * Cria o arquivo xml com extensão .jbt quem guarda
     * informações da cut, bem como o classpath. Sem
     * esse arquivo, não é possivel instrumentar a cut
     * @param cut
     */
    public boolean criarProjeto() {
        String[] args = {
            "-b", cut.basename(),
            "-path", cut.getNovoPath(),
            "-p", cut.getNovoPath() + cut.projectName()
        };

        try {
            CreateProject.main(args);
        } catch (Throwable ex) {
            Logger.getLogger(Jabuti.class.getName()).log(Level.SEVERE, null, ex);
            return false;
        }
        return true;
    }

    /**
     * Realiza a instrumentação da cut. Objetos responsáveis:
     * cut, projeto, arquivo .jar que será gerado
     * @param cut
     */
    public boolean instrumentarCut() {

        String[] args = {
            "-d", cut.getNovoPath(),
            "-o", cut.getNovoPath() + cut.classeInstrumentada(),
            "-p", cut.projectName(),
            "-nomain", cut.basename()
        };

        try {
            ProberInstrum.main(args);
            return true;
        } catch (Throwable ex) {
            Logger.getLogger(Jabuti.class.getName()).log(Level.SEVERE, null, ex);
            return false;
        }
    }

    public boolean extrairInformacoes(DadoTestePO dado) {

        String local = new File(".").getAbsolutePath();
        local = local.substring(0, local.length() - 1);

        String[] args = {
            "-cp", cut.getNovoPath() + cut.classeInstrumentada() + ":" + cut.getNovoPath() + ":" + local + "lib/Jabuti-bin.zip",
            "-tcClass", dado.nomeJUnit()
        };

        try {
            JUnitJabutiCore.main(args);
            return true;
        } catch (Throwable ex) {
            Logger.getLogger(Jabuti.class.getName()).log(Level.SEVERE, null, ex);
            return false;
        }
    }

    /**
     * JUnitJabutiCore -trace teste.trc -cp test:teste_instr.jar -tcClass Teste1
     * @return
     */
    public Long executarDadoTeste(DadoTestePO dado) {

        Long inicio;

        String local = new File(".").getAbsolutePath();
        local = local.substring(0, local.length() - 1);

        String pasta = pastaIndividuo(dado.getIndividuoId());

        String[] args = {
            "-cp", cut.getNovoPath() + cut.classeInstrumentada() + ":" + cut.getNovoPath() + ":" + local + "lib/Jabuti-bin.zip",
            "-trace", pasta + cut.traceFile(),
            "-tcClass", dado.nomeJUnit(), "testCase"
        };

        try {
            inicio = System.currentTimeMillis();
            JUnitJabutiCore.main(args);
            return System.currentTimeMillis() - inicio;
        } catch (Throwable ex) {
            Logger.getLogger(Jabuti.class.getName()).log(Level.SEVERE, null, ex);
            return 0L;
        }
    }

    /**
     * Serve para atualizar o projeto .jbt para que ele
     * tenha conhecimento do novo dado de teste.
     */
    public boolean atualizarProjeto(Individual ind) {
        String pasta = pastaIndividuo(ind.getId());
        System.out.println("Atualizando projeto do individuo: " + ind.getId());

        String[] args = {
            "-p", pasta + cut.projectName()
        };

        try {
            ImportTestCase.main(args);
            return true;
        } catch (Throwable ex) {
            Logger.getLogger(Jabuti.class.getName()).log(Level.SEVERE, null, ex);
            return false;
        }
    }

    /**
     * Gera os relatorios do dado de teste.
     * Tipos de relatorio: <br>
     *   all relatorio completo <br>
     *   pr  projeto <br>
     *   cl  classe <br>
     *   me  metodo <br>
     *   ts  test set <br>
     *   tc  test case
     * @param tipoRelatorio
     */
    public boolean gerarRelatorio(Individual ind, String tipoRelatorio) {
        String pasta = pastaIndividuo(ind.getId());

        String[] args = {
            "-p", cut.projectName(),
            "-d", pasta,
            "-" + tipoRelatorio, "-o", "completo.xml"
        };

        try {
            JabutiReport.main(args);
            return true;
        } catch (Throwable ex) {
            Logger.getLogger(Jabuti.class.getName()).log(Level.SEVERE, null, ex);
            return false;
        }
    }

    public double extrairResultados(Individual ind) {
        String pasta = pastaIndividuo(ind.getId());

        String report[] = Arquivo.leia(pasta + "completo.xml").split("\n");
        String result = null;
        Pattern p = null;
        Matcher m = null;

        for (int i = 0; i < report.length; i++) {
            if (report[i].contains(Constantes.CRITERIO)) {
                p = Pattern.compile("\\d+\\.\\d+");
                m = p.matcher(report[i]);
                if (m.find()) {
                    result = m.group();
                    break;
                }
            }
        }
        return Double.parseDouble(result);
    }

    private String pastaIndividuo(Integer id) {

        String pastaIndividuo = cut.getNovoPath() + "ind" + id + File.separator;

        if (isProjetoGerado(pastaIndividuo)) {
            return pastaIndividuo;
        }
        return cut.getNovoPath();
    }

    private boolean isProjetoGerado(String pasta) {
        File pastaIndividuo = new File(pasta);

        String arquivos[] = pastaIndividuo.list();
        for (int i = 0; i < arquivos.length; i++) {
            if (arquivos[i].endsWith(".jbt")) {
                return true;
            }
        }

        return false;
    }
}
